/*
 * Copyright (C) 2011 Michael Widmer <widmm8@bfh.ch>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ch.bfh.aeneas.navigation.guide;

import android.app.Service;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

/**
 * This is the backing service of GuideMe. It requests the addresses for the 
 * specified location from providers.
 * @author Michael Widmer <widmm8@bfh.ch>
 */
public class GuideService extends Service {

    /**
     * Components interested in status updates of this Service should be
     * implementing this interface and register themself on this service by
     * calling setListener.
     * Beware: This interface is not mandatory and so you may never receive any
     * updates, if service doesn't support this interface.
     * @see #setListener(ch.bfh.aeneas.navigation.guide.GuideService.OnStateListener)
     * @see #unsetListener(ch.bfh.aeneas.navigation.guide.GuideService.OnStateListener) 
     */
    public interface OnStateListener {
        public void onUpdate();
        public void onReady();
    }
    
    public class GuideServiceBinder extends Binder {

        public GuideService getService() {
            return GuideService.this;
        }
    }
    private final GuideServiceBinder mBinder = new GuideServiceBinder();
    
    @Override
    /**
     * {@inheritDoc}
     */
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    
    private SensorEventListener mSensorListener = new SensorEventListener() {

        public void onSensorChanged(SensorEvent se) {            
            if(se.sensor == mSensor) {
                float[] values = se.values;
                mAzimuth = Math.round(values[0]);
                mPitch = Math.round(values[1]);
                mRoll = Math.round(values[2]);
                
                if(!mReady) {
                    mReady = true;
                    informReadyListener();
                }
                informUpdateListener();
            }
        }

        public void onAccuracyChanged(Sensor arg0, int arg1) {
            Log.i(LOG_TAG, "Accuracy of sensor "+arg0.getName()+" changed to "+arg1);
        }        
    };
    
    private SensorManager mSensorManager;
    private Sensor mSensor;
    
    private String LOG_TAG = "CompassService";
    
    private float mAzimuth = 0; //N = 0, E = 90, S = 180, W = 270
    private float mPitch = 0; //vertical = 0, horizontal (left top) = 180, horizontal (right top) = -180
    private float mRoll = 0; //lefttop backwards = 90, lefftop forward = -90
    
    protected Location mTarget;
    protected OnStateListener mListener;
    protected boolean mReady;
    
    @Override
    /**
     * {@inheritDoc}
     */
    public void onCreate() {
        super.onCreate();
        
        mReady = false;
        
        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
        mSensorManager.registerListener(mSensorListener, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
    }
    
    @Override
    /**
     * {@inheritDoc}
     */
    public void onDestroy() {
        super.onDestroy();
        
        mSensorManager.unregisterListener(mSensorListener, mSensor);
    }
    
    /**
     * Indicates if the service is ready or not.
     * @return 
     */
    public boolean isReady() {
        return mReady;
    }
    
    /**
     * Sets the listener to passed instance.
     * @param listener 
     */
    public void setListener(OnStateListener listener) {
        mListener = listener;
        if(mReady)
            informUpdateListener();
    }
    
    /**
     * Unsets the passed listener if it was the one, that was registered.
     * @param listener 
     */
    public void unsetListener(OnStateListener listener) {
        if(mListener == listener) {
            mListener = null;
        }
    }
    
    protected void informReadyListener() {
        if(mListener != null) {
            mListener.onReady();
        }
    }
    
    protected void informUpdateListener() {
        if(mListener != null && mReady) {
            mListener.onUpdate();
        }
    }
    
    /**
     * Returns the current (absolute) bearing to the north.
     * @return 
     */
    public float getAzimuth() {
        return mAzimuth;
    }
    
    /**
     * Alias for @see #getAzimuth()
     * @return 
     */
    public float getAngle() {
        return mAzimuth;
    }
    
    public float getPitch() {
        return mPitch;
    }
    
    public float getRoll() {
        return mRoll;
    }
    
    /**
     * Set the target to passed instance.
     * @param target 
     */
    public void setTarget(Location target) {
        mTarget = target;
    }
    
    /**
     * Returns the relative bearing of the user to the specified target.
     * @param current
     * @return 
     */
    public float getRelativeBearing(Location current) {
        if(mTarget == null)
            return .0f;
        
        float targetBearing = (360+current.bearingTo(mTarget))%360;
        float currentBearing = mAzimuth;
        Log.i(LOG_TAG, "Target bearing: "+targetBearing+", current bearing: "+currentBearing);
        float difference = targetBearing - currentBearing;
        
        if(Math.abs(difference) > 180) {
            if(difference < 0) {
                difference = 360+difference;
            } else {
                difference = difference-360;
            }
        }        
        return difference;
    }
    
    /**
     * Returns the relative bearing to target in time form (e.g. left = 9 o'clock)
     * @param current
     * @return 
     */
    public int getTimeAngle(Location current) {
        float angle = getRelativeBearing(current);
        
        Log.i(LOG_TAG, "Working with angle "+angle);
        
        if(angle < 0) angle = 360+angle;
        
        Log.i(LOG_TAG, "angle after adaption: "+angle);
        
        int hour = (int) Math.round(angle/30);
        if(hour == 0) hour = 12;
        
        return hour;
    }
    
    /* 
     * http://android-developers.blogspot.com/2010/09/one-screen-turn-deserves-another.html
     * http://www.codingforandroid.com/2011/01/using-orientation-sensors-simple.html
    float[] mGravity;
    float[] mGeomagnetic;
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
            mGravity = event.values;
        if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
            mGeomagnetic = event.values;
        if (mGravity != null && mGeomagnetic != null) {
            float R[] = new float[9];
            float I[] = new float[9];
            boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
            if (success) {
                float orientation[] = new float[3];
                SensorManager.getOrientation(R, orientation);
                azimut = orientation[0]; // orientation contains: azimut, pitch and roll
            }
        }
    }//*/
}
